package com.yeskery.nut.util.http;

import com.yeskery.nut.core.*;
import com.yeskery.nut.util.IOUtils;
import com.yeskery.nut.util.StringUtils;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Http 工具类
 * @author sprout
 * 2022-05-19 18:19
 */
public class HttpClient implements HttpRequest, HttpStringRequest {

    /** 日志对象 */
    private static final Logger logger = Logger.getLogger(HttpClient.class.getName());

    /** 客户端信息 */
    private static final String USER_AGENT = "Nut/" + Version.VERSION + "(HttpClient)";

    @Override
    public ResponseEntity<byte[]> doGet(String httpUrl) {
        return doGet(httpUrl, Collections.emptyList());
    }

    @Override
    public ResponseEntity<byte[]> doGet(String httpUrl, List<NameAndValue> headers) {
        return doGet(httpUrl, headers, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public ResponseEntity<byte[]> doGet(String httpUrl, List<NameAndValue> headers, HttpConfiguration configuration) {
        return doRequestEntity(configuration, httpUrl, Method.GET.name(), false, c -> {
            if (headers != null && !headers.isEmpty()) {
                for (NameAndValue nameAndValue : headers) {
                    c.setRequestProperty(nameAndValue.getKey(), nameAndValue.getValue());
                }
            }
        }, null);
    }

    @Override
    public ResponseEntity<byte[]> doPost(String httpUrl, String body) {
        return doPost(httpUrl, Collections.emptyList(), body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public ResponseEntity<byte[]> doPost(String httpUrl, String body, MediaType mediaType) {
        return doPost(httpUrl, Collections.emptyList(), body, mediaType);
    }

    @Override
    public ResponseEntity<byte[]> doPost(String httpUrl, List<NameAndValue> headers, String body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequestEntity(configuration, httpUrl, Method.POST, headers, body, mediaType);
    }

    @Override
    public ResponseEntity<byte[]> doPost(String httpUrl, byte[] body) {
        return doPost(httpUrl, Collections.emptyList(), body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public ResponseEntity<byte[]> doPost(String httpUrl, byte[] body, MediaType mediaType) {
        return doPost(httpUrl, Collections.emptyList(), body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public ResponseEntity<byte[]> doPost(String httpUrl, List<NameAndValue> headers, byte[] body, MediaType mediaType) {
        return doPost(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public ResponseEntity<byte[]> doPost(String httpUrl, List<NameAndValue> headers, byte[] body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequestEntity(configuration, httpUrl, Method.POST, headers, body, mediaType);
    }

    @Override
    public ResponseEntity<byte[]> doPost(String httpUrl, InputStream body) {
        return doPost(httpUrl, body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public ResponseEntity<byte[]> doPost(String httpUrl, InputStream body, MediaType mediaType) {
        return doPost(httpUrl, Collections.emptyList(), body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public ResponseEntity<byte[]> doPost(String httpUrl, List<NameAndValue> headers, InputStream body, MediaType mediaType) {
        return doPost(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public ResponseEntity<byte[]> doPost(String httpUrl, List<NameAndValue> headers, InputStream body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequestEntity(configuration, httpUrl, Method.POST, headers, body, mediaType);
    }

    @Override
    public ResponseEntity<byte[]> doPost(String httpUrl, List<NameAndValue> headers, String body, MediaType mediaType) {
        return doPost(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public ResponseEntity<byte[]> doPut(String httpUrl, String body) {
        return doPut(httpUrl, body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public ResponseEntity<byte[]> doPut(String httpUrl, String body, MediaType mediaType) {
        return doPut(httpUrl, Collections.emptyList(), body, mediaType);
    }

    @Override
    public ResponseEntity<byte[]> doPut(String httpUrl, List<NameAndValue> headers, String body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequestEntity(configuration, httpUrl, Method.PUT, headers, body, mediaType);
    }

    @Override
    public ResponseEntity<byte[]> doPut(String httpUrl, byte[] body) {
        return doPut(httpUrl, Collections.emptyList(), body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public ResponseEntity<byte[]> doPut(String httpUrl, byte[] body, MediaType mediaType) {
        return doPut(httpUrl, Collections.emptyList(), body, mediaType);
    }

    @Override
    public ResponseEntity<byte[]> doPut(String httpUrl, List<NameAndValue> headers, byte[] body, MediaType mediaType) {
        return doPut(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public ResponseEntity<byte[]> doPut(String httpUrl, List<NameAndValue> headers, byte[] body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequestEntity(configuration, httpUrl, Method.PUT, headers, body, mediaType);
    }

    @Override
    public ResponseEntity<byte[]> doPut(String httpUrl, InputStream body) {
        return doPut(httpUrl, body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public ResponseEntity<byte[]> doPut(String httpUrl, InputStream body, MediaType mediaType) {
        return doPut(httpUrl, Collections.emptyList(), body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public ResponseEntity<byte[]> doPut(String httpUrl, List<NameAndValue> headers, InputStream body, MediaType mediaType) {
        return doPut(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public ResponseEntity<byte[]> doPut(String httpUrl, List<NameAndValue> headers, InputStream body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequestEntity(configuration, httpUrl, Method.PUT, headers, body, mediaType);
    }

    @Override
    public ResponseEntity<byte[]> doPut(String httpUrl, List<NameAndValue> headers, String body, MediaType mediaType) {
        return doPut(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public ResponseEntity<byte[]> doDelete(String httpUrl, String body) {
        return doDelete(httpUrl, body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public ResponseEntity<byte[]> doDelete(String httpUrl, String body, MediaType mediaType) {
        return doDelete(httpUrl, Collections.emptyList(), body, mediaType);
    }

    @Override
    public ResponseEntity<byte[]> doDelete(String httpUrl, List<NameAndValue> headers, String body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequestEntity(configuration, httpUrl, Method.DELETE, headers, body, mediaType);
    }

    @Override
    public ResponseEntity<byte[]> doDelete(String httpUrl, byte[] body) {
        return doDelete(httpUrl, Collections.emptyList(), body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public ResponseEntity<byte[]> doDelete(String httpUrl, byte[] body, MediaType mediaType) {
        return doDelete(httpUrl, Collections.emptyList(), body, mediaType);
    }

    @Override
    public ResponseEntity<byte[]> doDelete(String httpUrl, List<NameAndValue> headers, byte[] body, MediaType mediaType) {
        return doDelete(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public ResponseEntity<byte[]> doDelete(String httpUrl, List<NameAndValue> headers, byte[] body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequestEntity(configuration, httpUrl, Method.DELETE, headers, body, mediaType);
    }

    @Override
    public ResponseEntity<byte[]> doDelete(String httpUrl, InputStream body) {
        return doDelete(httpUrl, body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public ResponseEntity<byte[]> doDelete(String httpUrl, InputStream body, MediaType mediaType) {
        return doDelete(httpUrl, Collections.emptyList(), body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public ResponseEntity<byte[]> doDelete(String httpUrl, List<NameAndValue> headers, InputStream body, MediaType mediaType) {
        return doDelete(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public ResponseEntity<byte[]> doDelete(String httpUrl, List<NameAndValue> headers, InputStream body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequestEntity(configuration, httpUrl, Method.DELETE, headers, body, mediaType);
    }

    @Override
    public ResponseEntity<byte[]> doDelete(String httpUrl, List<NameAndValue> headers, String body, MediaType mediaType) {
        return doDelete(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public String doGetString(String httpUrl) {
        return doGetString(httpUrl, Collections.emptyList());
    }

    @Override
    public String doGetString(String httpUrl, List<NameAndValue> headers) {
        return doGetString(httpUrl, headers, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public String doGetString(String httpUrl, List<NameAndValue> headers, HttpConfiguration configuration) {
        return doRequest(configuration, httpUrl, Method.GET.name(), false, c -> {
            if (headers != null && !headers.isEmpty()) {
                for (NameAndValue nameAndValue : headers) {
                    c.setRequestProperty(nameAndValue.getKey(), nameAndValue.getValue());
                }
            }
        }, null);
    }

    @Override
    public String doPostString(String httpUrl, String body) {
        return doPostString(httpUrl, Collections.emptyList(), body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public String doPostString(String httpUrl, String body, MediaType mediaType) {
        return doPostString(httpUrl, Collections.emptyList(), body, mediaType);
    }

    @Override
    public String doPostString(String httpUrl, List<NameAndValue> headers, String body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequest(configuration, httpUrl, Method.POST, headers, body, mediaType);
    }

    @Override
    public String doPostString(String httpUrl, byte[] body) {
        return doPostString(httpUrl, Collections.emptyList(), body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public String doPostString(String httpUrl, byte[] body, MediaType mediaType) {
        return doPostString(httpUrl, Collections.emptyList(), body, mediaType);
    }

    @Override
    public String doPostString(String httpUrl, List<NameAndValue> headers, byte[] body, MediaType mediaType) {
        return doPostString(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public String doPostString(String httpUrl, List<NameAndValue> headers, byte[] body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequest(configuration, httpUrl, Method.POST, headers, body, mediaType);
    }

    @Override
    public String doPostString(String httpUrl, InputStream body) {
        return doPostString(httpUrl, Collections.emptyList(), body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public String doPostString(String httpUrl, InputStream body, MediaType mediaType) {
        return doPostString(httpUrl, Collections.emptyList(), body, mediaType);
    }

    @Override
    public String doPostString(String httpUrl, List<NameAndValue> headers, InputStream body, MediaType mediaType) {
        return doPostString(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public String doPostString(String httpUrl, List<NameAndValue> headers, InputStream body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequest(configuration, httpUrl, Method.POST, headers, body, mediaType);
    }

    @Override
    public String doPutString(String httpUrl, byte[] body) {
        return doPutString(httpUrl, body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public String doPutString(String httpUrl, byte[] body, MediaType mediaType) {
        return doPutString(httpUrl, Collections.emptyList(), body, mediaType);
    }

    @Override
    public String doPutString(String httpUrl, List<NameAndValue> headers, byte[] body, MediaType mediaType) {
        return doPutString(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public String doPutString(String httpUrl, List<NameAndValue> headers, byte[] body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequest(configuration, httpUrl, Method.PUT, headers, body, mediaType);
    }

    @Override
    public String doPutString(String httpUrl, InputStream body) {
        return doPutString(httpUrl, body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public String doPutString(String httpUrl, InputStream body, MediaType mediaType) {
        return doPutString(httpUrl, Collections.emptyList(), body, mediaType);
    }

    @Override
    public String doPutString(String httpUrl, List<NameAndValue> headers, InputStream body, MediaType mediaType) {
        return doPutString(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public String doPutString(String httpUrl, List<NameAndValue> headers, InputStream body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequest(configuration, httpUrl, Method.PUT, headers, body, mediaType);
    }

    @Override
    public String doPostString(String httpUrl, List<NameAndValue> headers, String body, MediaType mediaType) {
        return doPostString(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public String doPutString(String httpUrl, String body) {
        return doPutString(httpUrl, body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public String doPutString(String httpUrl, String body, MediaType mediaType) {
        return doPutString(httpUrl, Collections.emptyList(), body, mediaType);
    }

    @Override
    public String doPutString(String httpUrl, List<NameAndValue> headers, String body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequest(configuration, httpUrl, Method.PUT, headers, body, mediaType);
    }

    @Override
    public String doDeleteString(String httpUrl, byte[] body) {
        return doDeleteString(httpUrl, body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public String doDeleteString(String httpUrl, byte[] body, MediaType mediaType) {
        return doDeleteString(httpUrl, Collections.emptyList(), body, mediaType);
    }

    @Override
    public String doDeleteString(String httpUrl, List<NameAndValue> headers, byte[] body, MediaType mediaType) {
        return doDeleteString(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public String doDeleteString(String httpUrl, List<NameAndValue> headers, byte[] body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequest(configuration, httpUrl, Method.DELETE, headers, body, mediaType);
    }

    @Override
    public String doDeleteString(String httpUrl, InputStream body) {
        return doDeleteString(httpUrl, body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public String doDeleteString(String httpUrl, InputStream body, MediaType mediaType) {
        return doDeleteString(httpUrl, Collections.emptyList(), body, mediaType);
    }

    @Override
    public String doDeleteString(String httpUrl, List<NameAndValue> headers, InputStream body, MediaType mediaType) {
        return doDeleteString(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public String doDeleteString(String httpUrl, List<NameAndValue> headers, InputStream body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequest(configuration, httpUrl, Method.DELETE, headers, body, mediaType);
    }

    @Override
    public String doPutString(String httpUrl, List<NameAndValue> headers, String body, MediaType mediaType) {
        return doPutString(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    @Override
    public String doDeleteString(String httpUrl, String body) {
        return doDeleteString(httpUrl, body, MediaType.APPLICATION_X_WWW_FORM_URLENCODED);
    }

    @Override
    public String doDeleteString(String httpUrl, String body, MediaType mediaType) {
        return doDeleteString(httpUrl, Collections.emptyList(), body, mediaType);
    }

    @Override
    public String doDeleteString(String httpUrl, List<NameAndValue> headers, String body, MediaType mediaType, HttpConfiguration configuration) {
        return doBodyRequest(configuration, httpUrl, Method.DELETE, headers, body, mediaType);
    }

    @Override
    public String doDeleteString(String httpUrl, List<NameAndValue> headers, String body, MediaType mediaType) {
        return doDeleteString(httpUrl, headers, body, mediaType, HttpConfiguration.getDefaultHttpConfiguration());
    }

    /**
     * 执行含请求体的HTTP请求
     * @param httpUrl 请求url地址
     * @param method 请求方法
     * @param headers 请求头
     * @param body 请求体
     * @param mediaType 媒体类型
     * @return 响应结果
     */
    protected String doBodyRequest(String httpUrl, Method method, List<NameAndValue> headers, String body, MediaType mediaType) {
        return doBodyRequest(HttpConfiguration.getDefaultHttpConfiguration(), httpUrl, method, headers, body, mediaType);
    }

    /**
     * 执行含请求体的HTTP请求
     * @param configuration 配置对象
     * @param httpUrl 请求url地址
     * @param method 请求方法
     * @param headers 请求头
     * @param body 请求体
     * @param mediaType 媒体类型
     * @return 响应结果
     */
    protected String doBodyRequest(HttpConfiguration configuration, String httpUrl, Method method, List<NameAndValue> headers, String body, MediaType mediaType) {
        return doRequest(configuration, httpUrl, method.name(), !StringUtils.isEmpty(body), getRequestHeaderConsumer(headers, mediaType), o -> {
            try {
                o.write(body.getBytes(StandardCharsets.UTF_8));
            } catch (IOException e) {
                throw new NutException("HTTP Execute Fail.", e);
            }
        });
    }

    /**
     * 执行含请求体的HTTP请求
     * @param configuration 配置对象
     * @param httpUrl 请求url地址
     * @param method 请求方法
     * @param headers 请求头
     * @param body 请求体字节数组
     * @param mediaType 媒体类型
     * @return 响应结果
     */
    protected String doBodyRequest(HttpConfiguration configuration, String httpUrl, Method method, List<NameAndValue> headers, byte[] body, MediaType mediaType) {
        return doRequest(configuration, httpUrl, method.name(), body != null && body.length > 0, getRequestHeaderConsumer(headers, mediaType), o -> {
            try {
                o.write(body);
            } catch (IOException e) {
                throw new NutException("HTTP Execute Fail.", e);
            }
        });
    }

    /**
     * 执行含请求体的HTTP请求
     * @param configuration 配置对象
     * @param httpUrl 请求url地址
     * @param method 请求方法
     * @param headers 请求头
     * @param body 请求体输入流
     * @param mediaType 媒体类型
     * @return 响应结果
     */
    protected String doBodyRequest(HttpConfiguration configuration, String httpUrl, Method method, List<NameAndValue> headers, InputStream body, MediaType mediaType) {
        return doRequest(configuration, httpUrl, method.name(), body != null,
                getRequestHeaderConsumer(headers, mediaType), o -> IOUtils.transferInputStream(body, o));
    }

    /**
     * 执行含请求体的HTTP请求
     * @param configuration 配置对象
     * @param httpUrl 请求url地址
     * @param method 请求方法
     * @param headers 请求头
     * @param body 请求体
     * @param mediaType 媒体类型
     * @return 响应结果
     */
    protected ResponseEntity<byte[]> doBodyRequestEntity(HttpConfiguration configuration, String httpUrl, Method method, List<NameAndValue> headers, String body, MediaType mediaType) {
        return doRequestEntity(configuration, httpUrl, method.name(), !StringUtils.isEmpty(body), getRequestHeaderConsumer(headers, mediaType), o -> {
            try {
                o.write(body.getBytes(StandardCharsets.UTF_8));
            } catch (IOException e) {
                throw new NutException("HTTP Execute Fail.", e);
            }
        });
    }

    /**
     * 执行含请求体的HTTP请求
     * @param configuration 配置对象
     * @param httpUrl 请求url地址
     * @param method 请求方法
     * @param headers 请求头
     * @param body 请求体
     * @param mediaType 媒体类型
     * @return 响应结果
     */
    protected ResponseEntity<byte[]> doBodyRequestEntity(HttpConfiguration configuration, String httpUrl, Method method, List<NameAndValue> headers, byte[] body, MediaType mediaType) {
        return doRequestEntity(configuration, httpUrl, method.name(), body != null && body.length > 0, getRequestHeaderConsumer(headers, mediaType), o -> {
            try {
                o.write(body);
            } catch (IOException e) {
                throw new NutException("HTTP Execute Fail.", e);
            }
        });
    }

    /**
     * 执行含请求体的HTTP请求
     * @param configuration 配置对象
     * @param httpUrl 请求url地址
     * @param method 请求方法
     * @param headers 请求头
     * @param body 请求体
     * @param mediaType 媒体类型
     * @return 响应结果
     */
    protected ResponseEntity<byte[]> doBodyRequestEntity(HttpConfiguration configuration, String httpUrl, Method method, List<NameAndValue> headers, InputStream body, MediaType mediaType) {
        return doRequestEntity(configuration, httpUrl, method.name(), body != null,
                getRequestHeaderConsumer(headers, mediaType), o -> IOUtils.transferInputStream(body, o));
    }

    /**
     * 执行HTTP请求
     * @param httpUrl 请求url地址
     * @param method 请求方法
     * @param doOutput 是否含有请求体
     * @param requestConsumer 请求处理函数
     * @param responseConsumer 响应处理函数
     * @return 响应结果
     */
    protected String doRequest(String httpUrl, String method, boolean doOutput,
                               Consumer<HttpURLConnection> requestConsumer, Consumer<OutputStream> responseConsumer) {
        return doRequest(HttpConfiguration.getDefaultHttpConfiguration(), httpUrl, method, doOutput,
                requestConsumer, responseConsumer);
    }

    /**
     * 执行HTTP请求
     * @param configuration 配置对象
     * @param httpUrl 请求url地址
     * @param method 请求方法
     * @param doOutput 是否含有请求体
     * @param requestConsumer 请求处理函数
     * @param responseConsumer 响应处理函数
     * @return 响应结果
     */
    protected String doRequest(HttpConfiguration configuration, String httpUrl, String method, boolean doOutput,
                               Consumer<HttpURLConnection> requestConsumer, Consumer<OutputStream> responseConsumer) {
        return doRequestEntity(configuration, httpUrl, method, doOutput, requestConsumer, responseConsumer, i -> {
            try {
                try (BufferedReader br = new BufferedReader(new InputStreamReader(i, StandardCharsets.UTF_8))) {
                    StringBuilder sb = new StringBuilder();
                    String line;
                    while ((line = br.readLine()) != null) {
                        sb.append(line);
                        sb.append("\r\n");
                    }
                    return sb.toString();
                }
            } catch (IOException e) {
                throw new NutException("HTTP Execute Fail.");
            }
        }).getBody();
    }

    /**
     * 执行HTTP请求
     * @param configuration 配置对象
     * @param httpUrl 请求url地址
     * @param method 请求方法
     * @param doOutput 是否含有请求体
     * @param requestConsumer 请求处理函数
     * @param responseConsumer 响应处理函数
     * @return 响应结果
     */
    protected ResponseEntity<byte[]> doRequestEntity(HttpConfiguration configuration, String httpUrl,
                               String method, boolean doOutput, Consumer<HttpURLConnection> requestConsumer,
                                                     Consumer<OutputStream> responseConsumer) {
        return doRequestEntity(configuration, httpUrl, method, doOutput, requestConsumer, responseConsumer, IOUtils::readByteArray);
    }

    /**
     * 执行HTTP请求
     * @param configuration 配置对象
     * @param httpUrl 请求url地址
     * @param method 请求方法
     * @param doOutput 是否含有请求体
     * @param requestConsumer 请求处理函数
     * @param responseConsumer 响应处理函数
     * @param resultFunction 结果处理函数
     * @return 响应结果
     * @param <T> 泛型对象
     */
    protected <T> ResponseEntity<T> doRequestEntity(HttpConfiguration configuration, String httpUrl,
                                                     String method, boolean doOutput,
                                                     Consumer<HttpURLConnection> requestConsumer, Consumer<OutputStream> responseConsumer,
                                                     Function<InputStream, T> resultFunction) {
        HttpURLConnection connection = null;
        InputStream is = null;
        OutputStream os = null;
        try {
            connection = createHttpUrlConnection(httpUrl);
            connection.setRequestMethod(method);
            connection.setConnectTimeout(configuration.getConnectOutTime());
            connection.setReadTimeout(configuration.getReadOutTime());
            connection.setDoOutput(doOutput);
            connection.setUseCaches(false);
            connection.setRequestProperty(HttpHeader.USER_AGENT, USER_AGENT);
            if (requestConsumer != null) {
                requestConsumer.accept(connection);
            }
            if (doOutput) {
                os = connection.getOutputStream();
                if (responseConsumer != null) {
                    responseConsumer.accept(os);
                }
            }

            ResponseEntity<T> responseEntity = getResponseEntity(connection);
            try {
                is = connection.getInputStream();
                responseEntity.setBody(resultFunction.apply(is));
            } catch (IOException e) {
                is = connection.getErrorStream();
                responseEntity.setBody(resultFunction.apply(is));
            }
            return responseEntity;
        } catch (Exception e) {
            if (connection == null) {
                throw new NutException("HTTP Execute Fail.", e);
            }
            logger.log(Level.WARNING, e.getMessage());
            try {
                return getResponseEntity(connection);
            } catch (IOException ex) {
                throw new NutException("HTTP Execute Fail.", e);
            }
        } finally {
            close(os, is, connection);
        }
    }

    /**
     * 创建HttpURLConnection
     * @param httpUrl http连接
     * @return HTTP 连接
     * @throws IOException IOException
     */
    protected HttpURLConnection createHttpUrlConnection(String httpUrl) throws Exception {
        URL url = new URL(httpUrl);
        return (HttpURLConnection) url.openConnection();
    }

    /**
     * 关闭连接
     * @param os OutputStream
     * @param is InputStream
     * @param connection URLConnection
     */
    private void close(OutputStream os, InputStream is, HttpURLConnection connection) {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                logger.warning("HTTP Execute Resource Close Fail. Cause: " + e.getMessage());
            }
        }

        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                logger.warning("HTTP Execute Resource Close Fail. Cause: " + e.getMessage());
            }
        }

        if (connection != null) {
            connection.disconnect();
        }
    }

    /**
     * 获取响应实体对象，不包含响应体
     * @param connection 连接对象
     * @return 响应实体对象
     * @throws IOException 发生IO异常
     */
    private <T> ResponseEntity<T> getResponseEntity(HttpURLConnection connection) throws IOException {
        ResponseEntity<T> responseEntity = new ResponseEntity<>();
        Map<String, List<String>> map = connection.getHeaderFields();
        List<NameAndValue> headers = new ArrayList<>(map.size());
        for (Map.Entry<String, List<String>> entry : map.entrySet()) {
            if (entry.getKey() != null) {
                headers.add(new NameAndValue(entry.getKey(), entry.getValue() == null ? "" : String.join("; ", entry.getValue())));
            }
        }
        responseEntity.setHeaders(headers);
        responseEntity.setStatus(connection.getResponseCode());
        return responseEntity;
    }

    /**
     * 获取请求头处理函数
     * @param headers 请求头
     * @param mediaType 媒体类型
     * @return 请求头处理函数
     */
    private Consumer<HttpURLConnection> getRequestHeaderConsumer(List<NameAndValue> headers, MediaType mediaType) {
        return c -> {
            if (headers != null && !headers.isEmpty()) {
                for (NameAndValue nameAndValue : headers) {
                    c.setRequestProperty(nameAndValue.getKey(), nameAndValue.getValue());
                }
            }
            if (mediaType != null) {
                c.setRequestProperty(HttpHeader.CONTENT_TYPE, mediaType.getValue());
            }
        };
    }
}
